using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics;

namespace YEngineLib.Util
{
    /// <summary>
    /// All Entity states MUST inherit from this.
    /// All states inheriting from this MUST be singletons for ease of use.
    /// </summary>
    /// <typeparam name="T">The REAL class type of the entity who will use this state.</typeparam>
    public interface IYEntityState<T>
    {
        string Name { get; set; }

        //  The 3 E's of the Finite State Machine:

        /// <summary>
        ///  Called once EVERY time the state is entered.
        ///  Sets up the current state before Execute can be run.
        /// </summary>
        /// <param name="entity">The entity to process.</param>
        void Enter(T entity);


        /// <summary>
        ///  Called EVERY UPDATE.
        ///  Does all logic for the state.
        ///  Looks for conditions to change the current state.
        /// </summary>
        /// <param name="entity">The entity to process.</param>
        void Execute(T entity);


        /// <summary>
        ///  Called once EVERY time the state is exited.
        ///  Cleans up the current state before Enter() is called on the next state.
        /// </summary>
        /// <param name="entity">The entity to process.</param>
        void Exit(T entity);
    }

    /// <summary>
    /// Handles the registering, updating, and changing of states for the Entity.
    /// </summary>
    /// <typeparam name="T">The REAL class type of the entity who uses this state machine.</typeparam>
    public class YEntityStateMachine<T>
    {
        private T owner;

        private Dictionary<string, IYEntityState<T>> registeredStates = new Dictionary<string, IYEntityState<T>>();

        /// <summary>
        /// State that is run EVERY update.  Should contain code to handle logic
        /// that happen REGARDLESS of what the current state is.
        /// </summary>
        public IYEntityState<T> GlobalState { get; set; }

        /// <summary>
        /// The state the entity is currently in.
        /// </summary>
        public IYEntityState<T> CurrentState { get; private set; }

        /// <summary>
        /// The state we were in previously before changing to the current one.
        /// Can be used to fall back into from the current state.
        /// </summary>
        public IYEntityState<T> PreviousState { get; private set; }

        /// <summary>
        /// How long, in seconds, the current state has been running for.
        /// </summary>
        public float TimeInState
        {
            get;
            private set;
        }

        /// <summary>
        /// Creates the state machine.
        /// </summary>
        /// <param name="owner">The entity that this state machine belongs to.</param>
        public YEntityStateMachine(T owner)
        {
            this.owner = owner;
        }

        /// <summary>
        /// Registers the state with the state machine.
        /// This allows for the state machine to change to this state.
        /// </summary>
        /// <param name="stateName">The name of the state being registered.</param>
        /// <param name="state">The state object to register with the state name.</param>
        public void RegisterState(string stateName, IYEntityState<T> state)
        {
            state.Name = stateName;//.ToLower();

            registeredStates.Add(state.Name.ToLower(), state);
        }

        /// <summary>
        /// Returns the state object that was registered with the state machine,
        /// or null if the state is not found.
        /// </summary>
        /// <param name="stateName">Name of the state to get.</param>
        /// <returns></returns>
        public IYEntityState<T> GetState(string stateName)
        {
            string name = stateName.ToLower();
            IYEntityState<T> state;

            if (registeredStates.TryGetValue(name, out state))
                return state;
            else
                return null;
            //return registeredStates[stateName.ToLower()];
        }

        /// <summary>
        /// Updates the current state.
        /// </summary>
        /// <param name="dt">Fraction of time that has passed (in seconds).</param>
        public void Update(float dt)
        {
            TimeInState += dt;

            //if a global state exists, call its execute method
            if (GlobalState != null)
                GlobalState.Execute(owner);

            //same for the current state
            if (CurrentState != null)
                CurrentState.Execute(owner);
        }

        /// <summary>
        /// Changes the state of the Entity.
        /// </summary>
        /// <param name="stateName">Name of the state to change to.</param>
        public void ChangeState(string stateName)
        {
            IYEntityState<T> newState = GetState(stateName);

            Debug.Assert(newState != null, "<YEntityStateMachine::ChangeState>: trying to change to a null state: " + stateName);

            //keep a record of the previous state
            PreviousState = CurrentState;

            //call the exit method of the existing state
            if (CurrentState != null)
                CurrentState.Exit(owner);

            //change state to the new state
            CurrentState = newState;

            //  Reset time in state
            TimeInState = 0.0f;

            //call the entry method of the new state
            if (CurrentState != null)
                CurrentState.Enter(owner);
        }

        public void RevertToPreviousState()
        {
            ChangeState(PreviousState.Name);
        }
    }
}


